/*
 * Tranquil Java Integrated Development Environment
 *
 * The GNU General Public License Version 3
 *
 * Copyright (C) 2021 Autumn Lamonte
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Autumn Lamonte [AutumnWalksTheLake@gmail.com] ⚧ Trans Liberation Now
 * @version 1
 */
package tjide.ui;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

import gjexer.TApplication;
import gjexer.TCommand;
import gjexer.TExceptionDialog;
import gjexer.TInputBox;
import gjexer.TMessageBox;
import gjexer.bits.CellAttributes;
import gjexer.event.TCommandEvent;
import gjexer.event.TKeypressEvent;
import gjexer.event.TResizeEvent;
import gjexer.ttree.TTreeViewWindow;
import static gjexer.TCommand.*;
import static gjexer.TKeypress.*;

import tjide.project.ArchiveTarget;
import tjide.project.FileTarget;
import tjide.project.JavaTarget;
import tjide.project.Project;
import tjide.project.ProjectTarget;
import tjide.project.Target;

/**
 * This window exposes the project targets, with options to add, delete,
 * and rename them.
 */
public class ProjectWindow extends TTreeViewWindow {

    /**
     * Translated strings.
     */
    private static ResourceBundle i18n = ResourceBundle.getBundle(ProjectWindow.class.getName());

    // ------------------------------------------------------------------------
    // Constants --------------------------------------------------------------
    // ------------------------------------------------------------------------

    private static final TCommand cmEditTarget          = new TCommand(1000);
    private static final TCommand cmRenameTarget        = new TCommand(1001);
    private static final TCommand cmAddTarget           = new TCommand(1002);
    private static final TCommand cmDeleteTarget        = new TCommand(1003);
    private static final TCommand cmTargetOptions       = new TCommand(1004);

    // ------------------------------------------------------------------------
    // Variables --------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * The project being worked on.
     */
    private Project project;

    /**
     * The root tree item.
     */
    private ProjectWindowTreeItem root;

    /**
     * A map of directories to tree items.  Note package private access.
     */
    Map<File, ProjectWindowTreeItem> dirItems;

    // ------------------------------------------------------------------------
    // Constructors -----------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Public constructor.
     *
     * @param parent the main application
     * @param project the project
     */
    public ProjectWindow(final TApplication parent, final Project project) {

        super(parent, i18n.getString("windowTitle"),
            /*
            0, parent.getDesktopBottom() - 11,
            parent.getScreen().getWidth(), 10,
             */
            project.getWindowLeft(), project.getWindowTop() - 1,
            project.getWindowWidth(), project.getWindowHeight(),
            RESIZABLE | ABSOLUTEXY, null);

        this.project = project;

        dirItems = new HashMap<File, ProjectWindowTreeItem>();

        // Load the treeview
        root = new ProjectWindowTreeItem(this, project);
        for (Target target: project.getTargets()) {
            root.addChildTarget(this, target);
        }

        // Close all the leaf directories in the treeview
        for (ProjectWindowTreeItem dir: dirItems.values()) {
            boolean closeDir = true;
            for (ProjectWindowTreeItem item: dir.subItems) {
                if (item.getTarget() == null) {
                    // This directory contains another directory, keep it
                    // open.
                    closeDir = false;
                    break;
                }
            }
            if (closeDir) {
                dir.setExpanded(false);
                dir.onExpand();
            }
        }
        reflowData();
        getTreeView().setSelected(root, true);

        // Create a status bar
        statusBar = newStatusBar(i18n.getString("statusBar"));
        statusBar.addShortcutKeypress(kbIns, cmAddTarget,
            i18n.getString("statusBarInsert"));
        statusBar.addShortcutKeypress(kbDel, cmDeleteTarget,
            i18n.getString("statusBarDelete"));
        statusBar.addShortcutKeypress(kbCtrlO, cmTargetOptions,
            i18n.getString("statusBarOptions"));
        statusBar.addShortcutKeypress(kbCtrlR, cmRenameTarget,
            i18n.getString("statusBarRename"));
        statusBar.addShortcutKeypress(kbEnter, cmEditTarget,
            i18n.getString("statusBarEdit"));
        statusBar.addShortcutKeypress(kbF10, cmMenu,
            i18n.getString("statusBarMenu"));

    }

    // ------------------------------------------------------------------------
    // Event handlers ---------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Close the project when the project window is closed.
     */
    @Override
    public void onPreClose() {
        super.onPreClose();
        ((TranquilApplication) getApplication()).closeProject(true, true);
    }

    /**
     * Handle keystrokes.
     *
     * @param keypress keystroke event
     */
    @Override
    public void onKeypress(final TKeypressEvent keypress) {
        if (keypress.equals(kbIns)) {
            new NewTargetWindow(this);
        } else if (keypress.equals(kbDel)) {
            deleteTarget();
        } else if (keypress.equals(kbCtrlO)) {
            editTargetOptions();
        } else if (keypress.equals(kbCtrlR)) {
            renameTarget();
        } else if (keypress.equals(kbEnter)) {
            editTarget();
        } else {
            // I didn't take it, pass it on.
            super.onKeypress(keypress);
        }
    }

    /**
     * Handle posted command events.
     *
     * @param command command event
     */
    @Override
    public void onCommand(final TCommandEvent command) {
        if (command.equals(cmAddTarget)) {
            new NewTargetWindow(this);
        } else if (command.equals(cmDeleteTarget)) {
            deleteTarget();
        } else if (command.equals(cmTargetOptions)) {
            editTargetOptions();
        } else if (command.equals(cmRenameTarget)) {
            renameTarget();
        } else if (command.equals(cmEditTarget)) {
            editTarget();
        } else {
            // I didn't take it, pass it on.
            super.onCommand(command);
        }
    }

    /**
     * Handle window/screen resize events.
     *
     * @param event resize event
     */
    @Override
    public void onResize(final TResizeEvent event) {
        super.onResize(event);

        if (event.getType() == TResizeEvent.Type.WIDGET) {
            project.setWindowTop(getY());
            project.setWindowLeft(getX());
            project.setWindowWidth(getWidth());
            project.setWindowHeight(getHeight());
        }
    }

    // ------------------------------------------------------------------------
    // TWindow ----------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Retrieve the background color.
     *
     * @return the background color
     */
    @Override
    public final CellAttributes getBackground() {
        return getTheme().getColor("projectWindow.background");
    }

    /**
     * Retrieve the border color.
     *
     * @return the border color
     */
    @Override
    public CellAttributes getBorder() {
        if (inWindowMove) {
            return getTheme().getColor("projectWindow.windowMove");
        }
        return getTheme().getColor("projectWindow.background");
    }

    /**
     * Retrieve the color used by the window movement/sizing controls.
     *
     * @return the color used by the zoom box, resize bar, and close box
     */
    @Override
    public CellAttributes getBorderControls() {
        return getTheme().getColor("projectWindow.borderControls");
    }

    // ------------------------------------------------------------------------
    // ProjectWindow ----------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Get the project associated with this window.
     *
     * @return the project associated with this window
     */
    public Project getProject() {
        return project;
    }

    /**
     * Open the selected target in the appropriate editor.
     */
    private void editTarget() {
        Target target = ((ProjectWindowTreeItem) getSelected()).getTarget();

        if (target instanceof FileTarget) {
            TranquilApplication app = (TranquilApplication) getApplication();
            try {
                app.findTargetEditor(project, (FileTarget) target);
            } catch (IOException e) {
                new TExceptionDialog(getApplication(), e);
            }
            return;
        }
        if (target instanceof ArchiveTarget) {
            editTargetOptions();
            return;
        }
    }

    /**
     * Prompt to rename the selected target in the project.
     */
    private void renameTarget() {
        Target target = ((ProjectWindowTreeItem) getSelected()).getTarget();

        if (target == null) {
            // This is a directory, do not rename it.
            return;
        }

        TInputBox inputBox = getApplication().inputBox(i18n.
            getString("renameTargetInputBoxTitle"),
            i18n.getString("renameTargetInputBoxCaption"), target.getName(),
            TInputBox.Type.OKCANCEL);

        if (inputBox.getResult() == TInputBox.Result.OK) {
            target.setName(inputBox.getText().trim());
            if (target instanceof FileTarget) {
                getSelected().setText(new File(target.getName()).getName());
            } else {
                getSelected().setText(target.getName());
            }
        }
    }

    /**
     * Prompt to delete the selected target in the project.
     */
    public void deleteTarget() {
        Target target = ((ProjectWindowTreeItem) getSelected()).getTarget();

        if (target == null) {
            // This is a directory entry, do not remove it.
            return;
        }

        if (target instanceof ProjectTarget) {
            // We do not delete the root.
            return;
        }

        TMessageBox messageBox = getApplication().messageBox(i18n.
            getString("deleteTargetMsgBoxTitle"),
            MessageFormat.format(i18n.getString("deleteTargetMsgBoxText"),
                target), TMessageBox.Type.YESNO);

        if (messageBox.isYes()) {
            project.removeTarget(target);
            ((ProjectWindowTreeItem) getSelected()).deleteItem();
            getTreeView().onKeypress(new TKeypressEvent(kbUp));
            reflowData();
        }
    }

    /**
     * Edit the selected target's options.
     */
    public void editTargetOptions() {
        Target target = ((ProjectWindowTreeItem) getSelected()).getTarget();
        if (target != null) {
            new TargetOptionsWindow(getApplication(), target);
        }
    }

    /**
     * Compile the selected target.
     */
    public void compileTarget() {
        Target target = ((ProjectWindowTreeItem) getSelected()).getTarget();
        if (target != null) {
            target.compile(project);
        }
    }

    /**
     * Add a new target in this project.  Note package private access.
     *
     * @param targetName the name of the target to insert.  This can be
     * either a path, or a Java class name.
     * @param targetType the type of target to add
     * @return the new target, or null if not added
     */
    Target addTarget(String targetName,
        final NewTargetWindow.TargetType targetType) {

        // System.err.println("addTarget() " + targetName);

        // Remove leading "sourcedir/" string from target name.  Targets
        // added outside the source directory will be relativized below.
        if (project.getSourceDir().length() > 0) {
            if (targetName.startsWith(project.getSourceDir() + File.separator)) {
                targetName = targetName.substring(project.getSourceDir().
                    length() + 1);
            }
        }
        // Strip leading "./", which will happen on the initial project
        // source directory load.
        if (targetName.startsWith("./")) {
            targetName = targetName.substring(2);
        }

        File targetFile = null;
        if (targetName.equals(".")) {
            targetFile = new File(project.getRootDir(), targetName);
        } else {
            targetFile = new File(project.getFullSourceDir(), targetName);
        }
        if (targetFile.isAbsolute()) {
            File base = new File(project.getFullSourceDir()).getAbsoluteFile();
            String targetRelPath = null;
            try {
                targetRelPath = ProjectOptionsWindow.getRelativePath(base,
                    targetFile);
            } catch (IOException e) {
                new TExceptionDialog(getApplication(), e);
                return null;
            }
            if (targetRelPath != null) {
                targetName = targetRelPath;
                targetFile = new File(project.getFullSourceDir(), targetName);
            }
        } else {
            targetFile = new File(project.getFullSourceDir(), targetName);
        }

        /*
        System.err.println("targetFile: " + targetFile);
        System.err.println("    exists: " + targetFile.exists());
        System.err.println("     isDir: " + targetFile.isDirectory());
         */

        if (targetFile.exists() && targetFile.isDirectory()) {
            // targetFile is a directory, add all of the files inside of it.
            String [] subFiles = targetFile.list();
            if (subFiles != null) {
                for (int i = 0; i < subFiles.length; i++) {
                    if (subFiles[i].endsWith(".java")) {
                        addTarget(targetName + File.separator + subFiles[i],
                            NewTargetWindow.TargetType.JAVA_SOURCE);
                    } else if (subFiles[i].endsWith(".properties")) {
                        addTarget(targetName + File.separator + subFiles[i],
                            NewTargetWindow.TargetType.TEXT);
                    } else if (subFiles[i].endsWith(".txt")) {
                        addTarget(targetName + File.separator + subFiles[i],
                            NewTargetWindow.TargetType.TEXT);
                    } else if (subFiles[i].endsWith(".md")) {
                        addTarget(targetName + File.separator + subFiles[i],
                            NewTargetWindow.TargetType.TEXT);
                    } else if (subFiles[i].endsWith(".jar")) {
                        addTarget(targetName + File.separator + subFiles[i],
                            NewTargetWindow.TargetType.JAR);
                    } else {
                        addTarget(targetName + File.separator + subFiles[i],
                            targetType);
                    }
                }
            }
            return null;
        }

        // Change "package.of.classname" into "package/of/classname.java" .
        if (targetType == NewTargetWindow.TargetType.JAVA_SOURCE) {
            if (targetName.endsWith(".class")) {
                targetName = targetName.substring(0,
                    targetName.length() - 6);
                targetName += ".java";
            }
            if (targetName.endsWith(".java")) {
                targetName = targetName.substring(0,
                    targetName.length() - 5);
                targetName = targetName.replaceAll("([^\\.])\\.([^\\.])",
                    "$1/$2");
                targetName += ".java";
            }
            if (!targetName.endsWith(".java")) {
                return null;
            }
        }

        File targetPath = targetFile.getParentFile();
        if ((targetPath.exists()) && (!targetPath.isDirectory())) {
            // Error: path already exists and is not a directory.
            getApplication().messageBox(i18n.
                getString("cannotCreateDirTitle"),
                MessageFormat.format(i18n.
                    getString("cannotCreateDirCaption"),
                    targetPath.getName()));
            return null;
        } else if (!targetPath.exists()) {
            // Need to create path for this target.
            TMessageBox prompt = getApplication().messageBox(i18n.
                getString("askToCreateDirTitle"),
                MessageFormat.format(i18n.
                    getString("askToCreateDirCaption"),
                    targetPath.getName()),
                TMessageBox.Type.YESNO);

            if (prompt.isYes()) {
                // User gave permission, create the parent directory.
                targetPath.mkdirs();
            } else {
                // User cancelled, bail out.
                return null;
            }
        }

        if (project.hasFileTarget(targetName)) {
            // Don't add the same target twice.
            return null;
        }

        Target target = null;
        switch (targetType) {
        case JAVA_SOURCE:
            target = project.addJavaTarget(targetName);
            File javaFile = new File(project.getRootDir(), targetName);
            if (!javaFile.exists()) {
                project.writeJavaLicenseHeader(javaFile);
            }
            break;
        case TEXT:
            target = project.addTextTarget(targetName);
            break;
        case JAR:
            target = project.addJarTarget(targetName);
            break;
        }
        root.addChildTarget(this, target);
        reflowData();

        return target;
    }

    /**
     * Set the name of the project root item.
     *
     * @param name the new name
     */
    public void setProjectName(final String name) {
        root.setText(name);
        root.getTarget().setName(name);
    }

}
